﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NeuralNetworks.Neurons
{
    public class SomNeuron : Neuron
    {
        private readonly int _x; // pozycja x w 2D warstwie Kohonena
        private readonly int _y; // pozycja y w 2D warstwie Kohonena (w 1D = 0)
        private double _f; // tzw. frekwencja zwycięstw
        private double _d = 0.0;
        private readonly int _n; // liczba neuronów w warstwie Kohonena
        private int _neighbourhoodDist;

        /*private double _learningFactor;
        private double _conscience;*/

        public int X
        {
            get { return _x; }
            //set { _x = value; }
        }
        public int Y
        {
            get { return _y; }
            //set { _y = value; }
        }
        public double F
        {
            get { return _f; }
        }
        public double D
        {
            get { return _d; }
        }
        public int NeighbourhoodDist
        {
            get { return _neighbourhoodDist; }
            set { _neighbourhoodDist = value; }
        }

        public SomNeuron(int x, int y, double n)
        {
            _x = x;
            _y = y;
            _n = (int)n;
            _f = 1.0/n;
            _biasWeight = 0.0;
            _output = 0.0;
            _d = 0.0;
        }

        public override void Compute()
        {
            // Di
            double[] enter = new double[_inputs.Count];
            double[] weights = new double[_inputs.Count];
            for(int i=0; i<_inputs.Count; i++)
            {
                Connection c = _inputs[i] as Connection;
                enter[i] = c.N.Output;
                weights[i] = c.Weight;
            }
            NormalizeVector(enter);
            NormalizeVector(weights);
            _d = Math.Sqrt(EuclideanDist2(enter, weights));
            _d = _d + _biasWeight;
            _output = _d;
        }

        public void Learn(int winnerX, int winnerY, int epoch, double conscience, double learningFactor, double beta)
        {
            // Fi
            if (_x == winnerX && _y == winnerY)
            {
                _f = _f + beta * (1.0 - _f);
            }
            else
            {
                _f = _f + beta * (0.0 - _f);
            }
            // Bi
            _biasWeight = conscience * (_n * _f - 1.0);
            // Wij
            double dist = Math.Sqrt(EuclideanDist2(new double[] { _x, _y }, new double[] { winnerX, winnerY }));
            if (dist <= _neighbourhoodDist)
            {
                foreach (Connection c in _inputs)
                {
                    double gaussdist = Gauss(dist);
                    c.Weight = c.Weight + learningFactor * (c.N.Output - c.Weight) * gaussdist;
                }
            }
        }

        private double EuclideanDist2(double[] x, double[] y)
        {
            double sum = 0.0;
            double differ;
            for (int i = 0; i < x.Length; i++)
            {
                differ = x[i] - y[i];
                sum += differ * differ;
            }
            return sum;
        }

        private double VectorLength(double[] x)
        {
            double len = 0.0;
            for(int i = 0; i<x.Length; i++)
            {
                len += x[i]*x[i];
            }
            len = Math.Sqrt(len);
            return len;
        }
        private double[] NormalizeVector(double[] x)
        {
            double len = VectorLength(x);
            for(int i=0; i < x.Length; i++)
            {
                x[i] = x[i]/len;
            }
            return x;
        }

        // stałe dla przyspieszenia liczenia funkcji Gaussa
        private const double Sigma = 0.4;
        private readonly double _divi = Sigma * (Math.Sqrt(2.0 * Math.PI));

        private double Gauss(double x)
        {
            return Math.Exp(x*x/(-2.0*Sigma*Sigma))/_divi;
        }
    }
}
